/**
 * 题1：eventloop 代码执行顺序
 */
async function testSometing() {
  console.log("执行testSometing");
  return "testSometing";
}

async function testAsync() {
  console.log("执行testAsync");
  return Promise.resolve("hello async");
}

async function test() {
  console.log("test start...");
  new Promise(resolve => {
    console.log("async 里面的promise");
    resolve("async里面的promise里面的then");
  }).then(data => {
    console.log(data);
  });
  const v1 = await testSometing();
  console.log(v1);
  const v2 = await testAsync();
  console.log(v2);
  console.log(v1, v2);
}

test();

var promise = new Promise(resolve => {
  console.log("promise start..");
  resolve("promise");
});

promise.then(val => console.log(val));

setTimeout(() => {
  console.log("setTime1");
}, 3000);

console.log("test end...");

// test start...
// async 里面的promise
// promise start..
// test end...
// async里面的promise里面的then
// testSometing
// 执行testAsync
// promise
// hello async
// testSometing hello async
// setTime1 (3s后)

/**
 * 题2 this 指向问题
 */
function a(xx){
  this.x = xx;
  return this
};
var x = a(5);  // 此时把 window.x 设置为了 a() 的返回值 this，也就是指向 window 自己
console.log(x.x); // 此时 x, x.x, x.x.x, .... 都是指向 window 自身
var y = a(6);  // 此时把 window.y 设置为了 window 自身，并且修改了 window.x 设置为6
console.log(y.x, x); // 此时 y.x 就是指向 window.x, 和 x 一样值都是6
console.log(x.x); // 此时打印的是 undefined, x.x 相当于去查找 6['x'], 会把 6 隐式转换成包装类型 Number, 也就是在 Number.prototype 原型上查找['x']
Number.prototype['x'] = '我是Number原型上的变量x' // 此时 x.x 就被修改为 '我是Number原型上的变量x'

/**
 * 题3：闭包问题(五星难度李姐)
 * 对于fun方法，调用之后返回的是一个对象。我们知道，当调用函数的时候传入的实参比函数声明时指定的形参个数要少，剩下的形参都将设置为undefined值。
 * 所以 console.log(o); 会输出undefined。而a就是是fun(0)返回的那个对象。也就是说，函数fun中参数 n 的值是0，而返回的那个对象中，需要一个参数n，
 * 而这个对象的作用域中没有n，它就继续沿着作用域向上一级的作用域中寻找n，最后在函数fun中找到了n，n的值是0。
 */
function fun(n, o) {
  console.log(o)
  return {
    fun: function(m){
      return fun(m, n);
    }
  };
}
var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1);  c.fun(2);  c.fun(3);
// undefined  0  0  0
// undefined  0  1  2
// undefined  0  1  1

/**
 * == 比较对象、闭包函数覆盖问题
 * 1.这里首先定义了两个变量f和g，我们知道变量是可以重新赋值的。后面是一个匿名自执行函数，在 if 条件中调用了函数 g()，由于在匿名函数中，
 * 又重新定义了函数g，就覆盖了外部定义的变量g，所以，这里调用的是内部函数 g 方法，返回为 true。第一个条件通过，进入第二个条件。
 * 2.第二个条件是[] == ![]，先看 ![] ，当用于布尔运算时，对象的非空引用被视为 true，空引用 null 则被视为 false。[]不是一个空引用, 被视为 true, 
 * 而 ![] 的结果就是 false。当一个布尔值参与到条件运算的时候，true 会被看作 1, 而 false 会被看作 0。现在条件变成了 [] == 0 的问题了，当一个对
 * 象参与条件比较的时候，它会被 valueOf/toString 求值，数组使用 toString 求值成为一个字符串，[] 的结果就是 '' ，相当于 '' == 0，此时 ''会被转
 * 换成数值 0，所以 [] == ![] 成立
 * 3. IIFE 内部 if 条件成立，执行内部代码，f 没有使用 var/let 定义，所以视为全局变量，会通过闭包访问到外部的 f，并且覆盖原来的函数表达式
 */
f = function() { return true; };
g = function() { return false; };
(function() {
  if (g() && [] == ![]) {
    f = function f() { return false; }; // 没有 var 声明，会覆盖全局的 f
    function g() { return true; }  // 局部作用域函数声明提升，if 条件中的 g 执行的是当前的函数返回 true
  }
})();
console.log(f()); // 打印 false

/**
 * 原型链和构造方法问题
 * 1.Foo.getName()， Foo为一个函数对象，对象都可以有属性，b 处定义Foo的getName属性为函数，输出2；
 * 2.getName()， 这里看d、e处，d为函数表达式，e为函数声明，两者区别在于变量提升，函数声明的 5 会被后边函数表达式的 4 覆盖；
 * 3.Foo().getName()，这里要看a处，在Foo内部将全局的getName重新赋值为 console.log(1) 的函数，执行Foo()返回 this，这个this指向window，
 * Foo().getName() 即为window.getName()，输出 1；
 * 4.getName()， 上面3中，全局的getName已经被重新赋值，所以这里依然输出 1；
 * 5.new Foo.getName()， 这里等价于 new (Foo.getName())，先执行 Foo.getName()，输出 2，然后new一个实例；
 * 6.new Foo().getName()， 这 里等价于 (new Foo()).getName(), 先new一个Foo的实例，再执行这个实例的getName方法，但是这个实例本身没有这个方法，
 * 所以去原型链__protot__上边找，实例__protot__ === Foo.prototype，所以输出 3；
 * 7.new new Foo().getName()， 这里等价于new (new Foo().getName())，如上述6，先输出 3，然后new 一个 new Foo().getName() 的实例。
 */
// a
function Foo () {
  // 这里定义的是全局变量，覆盖了 window 下面的 getName
  getName = function () {
    console.log(1);
  }
  return this;
}
// b
Foo.getName = function () {
  console.log(2);
}
// c
Foo.prototype.getName = function () {
  console.log(3);
}
// d
var getName = function () {
  console.log(4);
}
// e 函数声明提升
function getName () {
  console.log(5);
}

 Foo.getName();           // 2
 getName();               // 4
 Foo().getName();         // 1
 getName();               // 1 
 new Foo.getName();       // 2
 new Foo().getName();     // 3
 new new Foo().getName(); // 3

/**
 * 原型 && 继承问题
 */
function Parent() {
  this.a = 1;
  this.b = [1, 2, this.a];
  this.c = { demo: 5 };
  this.show = function () {
      console.log(this.a , this.b , this.c.demo );
  }
}
function Child() {
  this.a = 2;
  this.change = function () {
      this.b.push(this.a);
      this.a = this.b.length;
      this.c.demo = this.a++;
  }
}
Child.prototype = new Parent();
var parent = new Parent();
var child1 = new Child();
var child2 = new Child();
child1.a = 11;
child2.a = 12;

parent.show(); // 1 [1, 2, 1], 5
child1.show(); // 11 [1, 2, 1], 5 
// 注：1.b是一个数组，里面 this.a 是常量值，而非引用值，所以 Parent 构造函数被调用时，b的值就已经被确定为[1,2,1]，
// 除非通过 this.b[2] = xx 赋值方式修改，所以 this.a 变化不会影响 this.b
// 2.赋值 child1.a = 11 相当于修改了 child1 实例中的 this.a 设置为了 11
child2.show(); // 12 [1, 2, 1], 5

child1.change(); 
// 1.首先把 child1.a 使用 push 添加到了 this.b(child1 没有属性 b, 所以修改的是 Child.prototype.b)， Child.prototype.b = [1,2,1,11]
// 2.修改 this.a(相当于 child1.a) 设置为了 4(Child.prototype.b.length)
// 3.修改 this.c.demo(child1 没有属性 c, 所以修改的是 Child.prototype.c.demo) 设置为了 this.a(child1.a)，等于4
// 4.最后把 this.a++，修改 child1.a = 5

child2.change();
// 1.同上，因为 child1 和 child2 的原型是同一个对象(同一个 parent 的实例)，所以 child1.change 修改了原型上的 b 和 c，对 child2 取值有影响
// 2.这时候 this.b(Child.prototype.b) 再 push，得到 [1,2,1,11,12]，然后 this.a(child2.a) 修改为 b 的长度 5
// 3.赋值给 this.c.demo，设置 Child.prototype.c.demo = 5
// 4.最后 this.a++，修改 child2.a 得到 6

parent.show(); // 1 [1, 2, 1], 5  
// 注：Child.prototype 指向的是 Parent 的一个实例，和当前 parent 是不同的实例，所以修改 Child.prototype 属性不影响 parent

child1.show(); // 5 [1,2,1,11,12] 5
child2.show(); // 6 [1,2,1,11,12] 5
// 1.打印 this.a 拿到的是 child1.a 和 child2.a
// 2.打印 this.b 和 this.c.demo 都是获取的 Child.prototype 上定义的 b 和 c.demo，拿到的是最终修改后得到的值 [1,2,1,11,12] 5